package org.eiahe.hr.attendance.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eiahe.hr.attendance.domain.*;
import org.eiahe.hr.attendance.mapper.*;
import org.eiahe.hr.attendance.service.IAttendanceAlgorithmService;
import org.eiahe.hr.attendance.service.IEmployeeShiftService;
import org.eiahe.hr.attendance.domain.vo.ShiftVo;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.List;

/**
 * 考勤核心算法Service实现
 *
 * @author zhangnn
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class AttendanceAlgorithmServiceImpl implements IAttendanceAlgorithmService {

    private final IEmployeeShiftService employeeShiftService;
    private final HolidayMapper holidayMapper;
    private final SpecialAttendanceRuleMapper specialRuleMapper;
    private final AttendanceRecordMapper attendanceRecordMapper;
    private final LeaveApplicationMapper leaveApplicationMapper;

    /**
     * 考勤状态判定算法
     */
    @Override
    public String determineAttendanceStatus(String employeeNo, LocalDate attendanceDate, LocalTime clockTime,
            String clockType) {
        log.debug("开始判定考勤状态: 工号={}, 日期={}, 时间={}, 类型={}", employeeNo, attendanceDate, clockTime, clockType);

        // 1. 检查是否是节假日或非工作日
        if (isHoliday(attendanceDate)) {
            log.debug("当前日期为节假日");
            return "6"; // 非工作日
        }

        // 2. 检查是否有请假记录
        if (hasLeaveApplication(employeeNo, attendanceDate)) {
            log.debug("员工当天有请假记录");
            return "4"; // 请假
        }

        // 3. 检查是否有特殊考勤规则(免打卡、弹性工作制等)
        SpecialAttendanceRule specialRule = getEffectiveSpecialRule(employeeNo, attendanceDate);
        if (specialRule != null) {
            log.debug("员工有生效的特殊考勤规则: {}", specialRule.getRuleType());
            // 免打卡规则,直接标记为正常
            if ("1".equals(specialRule.getRuleType())) {
                return "0"; // 正常
            }
        }

        // 4. 获取员工的班次信息
        ShiftVo shift = employeeShiftService.queryEmployeeCurrentShift(employeeNo, attendanceDate);
        if (shift == null) {
            log.warn("员工未分配班次,无法判定考勤状态: {}", employeeNo);
            return "0"; // 默认正常
        }

        // 5. 根据班次和打卡时间判定状态
        return determineStatusByShift(shift, clockTime, clockType, specialRule);
    }

    /**
     * 根据班次判定考勤状态
     */
    private String determineStatusByShift(ShiftVo shift, LocalTime clockTime, String clockType,
            SpecialAttendanceRule specialRule) {
        if ("1".equals(clockType)) {
            // 上班打卡
            LocalTime workStartTime = shift.getWorkStartTime();
            Integer lateThreshold = shift.getLateThresholdMinutes();

            // 特殊规则的容忍时长
            if (specialRule != null && specialRule.getToleranceMinutes() != null) {
                lateThreshold = specialRule.getToleranceMinutes();
            }

            long minutesLate = ChronoUnit.MINUTES.between(workStartTime, clockTime);

            if (minutesLate <= 0) {
                return "0"; // 正常
            } else if (minutesLate <= lateThreshold) {
                return "0"; // 在容忍范围内,正常
            } else {
                return "1"; // 迟到
            }
        } else if ("2".equals(clockType)) {
            // 下班打卡
            LocalTime workEndTime = shift.getWorkEndTime();
            Integer earlyThreshold = shift.getEarlyThresholdMinutes();

            long minutesEarly = ChronoUnit.MINUTES.between(clockTime, workEndTime);

            if (minutesEarly <= 0) {
                return "0"; // 正常
            } else if (minutesEarly <= earlyThreshold) {
                return "0"; // 在容忍范围内,正常
            } else {
                return "2"; // 早退
            }
        }

        return "0"; // 默认正常
    }

    /**
     * 计算请假天数(扣除午休、非工作日)
     */
    @Override
    public BigDecimal calculateLeaveDays(String employeeNo, LocalDateTime startTime, LocalDateTime endTime) {
        log.debug("开始计算请假天数: 工号={}, 开始时间={}, 结束时间={}", employeeNo, startTime, endTime);

        if (startTime == null || endTime == null || startTime.isAfter(endTime)) {
            throw new IllegalArgumentException("请假时间参数错误");
        }

        // 获取员工的班次信息(使用开始日期的班次)
        ShiftVo shift = employeeShiftService.queryEmployeeCurrentShift(employeeNo, startTime.toLocalDate());
        if (shift == null) {
            log.warn("员工未分配班次,无法精确计算请假天数,使用默认算法: {}", employeeNo);
            return calculateDefaultLeaveDays(startTime, endTime);
        }

        // 按天遍历计算
        BigDecimal totalDays = BigDecimal.ZERO;
        LocalDate currentDate = startTime.toLocalDate();
        LocalDate endDate = endTime.toLocalDate();

        while (!currentDate.isAfter(endDate)) {
            // 跳过节假日和周末
            if (isHoliday(currentDate) || isWeekend(currentDate)) {
                log.debug("跳过非工作日: {}", currentDate);
                currentDate = currentDate.plusDays(1);
                continue;
            }

            // 计算当天的请假时长
            LocalDateTime dayStart = currentDate.equals(startTime.toLocalDate())
                    ? startTime
                    : currentDate.atTime(shift.getWorkStartTime());

            LocalDateTime dayEnd = currentDate.equals(endTime.toLocalDate())
                    ? endTime
                    : currentDate.atTime(shift.getWorkEndTime());

            BigDecimal dayLeaveDays = calculateDayLeaveDays(shift, dayStart, dayEnd);
            totalDays = totalDays.add(dayLeaveDays);

            currentDate = currentDate.plusDays(1);
        }

        // 四舍五入到0.5天
        return totalDays.setScale(1, RoundingMode.HALF_UP);
    }

    /**
     * 计算单天的请假天数
     */
    private BigDecimal calculateDayLeaveDays(ShiftVo shift, LocalDateTime startTime, LocalDateTime endTime) {
        LocalTime dayWorkStart = shift.getWorkStartTime();
        LocalTime dayWorkEnd = shift.getWorkEndTime();
        LocalTime lunchStart = shift.getLunchStartTime();
        LocalTime lunchEnd = shift.getLunchEndTime();

        LocalTime leaveStart = startTime.toLocalTime();
        LocalTime leaveEnd = endTime.toLocalTime();

        // 限制在工作时间范围内
        if (leaveStart.isBefore(dayWorkStart)) {
            leaveStart = dayWorkStart;
        }
        if (leaveEnd.isAfter(dayWorkEnd)) {
            leaveEnd = dayWorkEnd;
        }

        // 计算总请假分钟数
        long totalMinutes = ChronoUnit.MINUTES.between(leaveStart, leaveEnd);

        // 扣除午休时间
        if (lunchStart != null && lunchEnd != null) {
            if (!leaveEnd.isBefore(lunchStart) && !leaveStart.isAfter(lunchEnd)) {
                LocalTime actualLunchStart = leaveStart.isAfter(lunchStart) ? leaveStart : lunchStart;
                LocalTime actualLunchEnd = leaveEnd.isBefore(lunchEnd) ? leaveEnd : lunchEnd;
                long lunchMinutes = ChronoUnit.MINUTES.between(actualLunchStart, actualLunchEnd);
                totalMinutes -= lunchMinutes;
            }
        }

        // 计算工作日总分钟数(扣除午休)
        long workMinutesPerDay = ChronoUnit.MINUTES.between(dayWorkStart, dayWorkEnd);
        if (lunchStart != null && lunchEnd != null) {
            workMinutesPerDay -= ChronoUnit.MINUTES.between(lunchStart, lunchEnd);
        }

        // 计算请假天数
        BigDecimal leaveDays = BigDecimal.valueOf(totalMinutes)
                .divide(BigDecimal.valueOf(workMinutesPerDay), 2, RoundingMode.HALF_UP);

        // 四舍五入到0.5天
        return leaveDays.setScale(1, RoundingMode.HALF_UP);
    }

    /**
     * 默认请假天数计算(无班次信息时)
     */
    private BigDecimal calculateDefaultLeaveDays(LocalDateTime startTime, LocalDateTime endTime) {
        long days = ChronoUnit.DAYS.between(startTime.toLocalDate(), endTime.toLocalDate());

        // 计算首尾天数
        BigDecimal totalDays = BigDecimal.valueOf(days);

        // 简化算法: 全天按1天,半天按0.5天
        if (startTime.toLocalTime().isAfter(LocalTime.of(12, 0))) {
            totalDays = totalDays.add(BigDecimal.valueOf(0.5));
        } else {
            totalDays = totalDays.add(BigDecimal.ONE);
        }

        return totalDays;
    }

    /**
     * 重复打卡处理逻辑
     */
    @Override
    public String handleDuplicateClock(AttendanceRecord record) {
        log.debug("检查重复打卡: 工号={}, 日期={}, 类型={}",
                record.getEmployeeNo(), record.getAttendanceDate(), record.getClockType());

        LambdaQueryWrapper<AttendanceRecord> lqw = Wrappers.lambdaQuery();
        lqw.eq(AttendanceRecord::getEmployeeNo, record.getEmployeeNo());
        lqw.eq(AttendanceRecord::getAttendanceDate, record.getAttendanceDate());
        lqw.eq(AttendanceRecord::getClockType, record.getClockType());

        // 如果是编辑,排除自己
        if (record.getRecordId() != null) {
            lqw.ne(AttendanceRecord::getRecordId, record.getRecordId());
        }

        List<AttendanceRecord> existingRecords = attendanceRecordMapper.selectList(lqw);

        if (existingRecords == null || existingRecords.isEmpty()) {
            return "0"; // 不是重复打卡
        }

        // 判断时间是否接近(30分钟内视为重复)
        for (AttendanceRecord existing : existingRecords) {
            long minutesDiff = Math.abs(ChronoUnit.MINUTES.between(
                    existing.getClockTime(), record.getClockTime()));

            if (minutesDiff <= 30) {
                log.info("检测到重复打卡: 工号={}, 原记录时间={}, 新记录时间={}",
                        record.getEmployeeNo(), existing.getClockTime(), record.getClockTime());
                return "1"; // 是重复打卡
            }
        }

        return "0"; // 不是重复打卡
    }

    /**
     * 综合处理考勤记录
     */
    @Override
    public void processAttendanceRecord(AttendanceRecord record) {
        log.debug("综合处理考勤记录: {}", record.getRecordId());

        // 1. 处理重复打卡
        String isDuplicate = handleDuplicateClock(record);
        record.setIsDuplicate(isDuplicate);

        // 2. 判定考勤状态(如果未手动设置)
        if (StringUtils.isBlank(record.getAttendanceStatus())) {
            String status = determineAttendanceStatus(
                    record.getEmployeeNo(),
                    record.getAttendanceDate(),
                    record.getClockTime(),
                    record.getClockType());
            record.setAttendanceStatus(status);
        }

        log.debug("处理完成: 重复打卡={}, 考勤状态={}", isDuplicate, record.getAttendanceStatus());
    }

    /**
     * 判断是否是节假日
     */
    private boolean isHoliday(LocalDate date) {
        LambdaQueryWrapper<Holiday> lqw = Wrappers.lambdaQuery();
        lqw.eq(Holiday::getHolidayDate, date);
        lqw.eq(Holiday::getStatus, "0");
        lqw.eq(Holiday::getIsCompensatory, "0"); // 非调休日
        Long count = holidayMapper.selectCount(lqw);
        return count != null && count > 0;
    }

    /**
     * 判断是否是周末
     */
    private boolean isWeekend(LocalDate date) {
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        return dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
    }

    /**
     * 检查是否有请假记录
     */
    private boolean hasLeaveApplication(String employeeNo, LocalDate date) {
        LambdaQueryWrapper<LeaveApplication> lqw = Wrappers.lambdaQuery();
        lqw.eq(LeaveApplication::getEmployeeNo, employeeNo);
        lqw.le(LeaveApplication::getStartTime, date.atTime(23, 59, 59));
        lqw.ge(LeaveApplication::getEndTime, date.atStartOfDay());
        lqw.eq(LeaveApplication::getApprovalStatus, "3"); // 已通过
        Long count = leaveApplicationMapper.selectCount(lqw);
        return count != null && count > 0;
    }

    /**
     * 获取生效的特殊考勤规则
     */
    private SpecialAttendanceRule getEffectiveSpecialRule(String employeeNo, LocalDate date) {
        LambdaQueryWrapper<SpecialAttendanceRule> lqw = Wrappers.lambdaQuery();
        lqw.eq(SpecialAttendanceRule::getEmployeeNo, employeeNo);
        lqw.le(SpecialAttendanceRule::getEffectiveDate, date);
        lqw.and(wrapper -> wrapper.isNull(SpecialAttendanceRule::getExpiryDate)
                .or()
                .ge(SpecialAttendanceRule::getExpiryDate, date));
        lqw.eq(SpecialAttendanceRule::getApprovalStatus, "3"); // 已通过
        lqw.orderByDesc(SpecialAttendanceRule::getEffectiveDate);
        lqw.last("LIMIT 1");

        return specialRuleMapper.selectOne(lqw);
    }
}
